
#include "CCSkeletalSprite.h"
#include "CCAttachment.h"
#include "CCSkeletonCache.h"
#include "CCSpineGlobal.h"
#include "log.h"
#include <queue>

NS_CC_BEGIN

CCSkeletalSprite::CCSkeletalSprite() :
m_skeleton(NULL),
m_animation(NULL),
m_loop(0),
m_paused(false),
m_rootBone(NULL),
_preAnimation(NULL),
_skin(NULL),
_blendAnimation(NULL),
_debugBone(false),
_debugSlots(false),
_debugBoundingBox(false),
_isFlipX(false) {
	_hp = NULL;
	_opacity = 255;
}

CCSkeletalSprite::~CCSkeletalSprite() {
	clearBoneStates(m_rootBone);
	cleanSlotStates();
	CC_SAFE_RELEASE(m_skeleton);
	CC_SAFE_RELEASE(m_animation);
	CC_SAFE_RELEASE(_preAnimation);
	CC_SAFE_RELEASE(_blendAnimation);
	for (auto it:_rootSpriteList) {
		it->release();
	}
	_rootSpriteList.clear();
}

CCSkeletalSprite *CCSkeletalSprite::create(const char *key) {
	CCSkeleton *s = CCSkeletonCache::getInstance()->getSkeleton(key);
	assert(s);
	return create(s);
}

CCSkeletalSprite *CCSkeletalSprite::create(CCSkeleton *s) {
	CCSkeletalSprite *sprite = new CCSkeletalSprite();
	sprite->setSkeleton(s);
	sprite->initUnit();
	return (CCSkeletalSprite *) sprite->autorelease();
}

void CCSkeletalSprite::initUnit() {
	CCSlot *slot = m_skeleton->getSlot("bl3");
	if (!slot) {
		return;
	}

	_hp = slot->getState(this).sprite;

	slot = m_skeleton->getSlot("bl2");
	_hpBg1 = slot->getState(this).sprite;

	slot = m_skeleton->getSlot("bl1");
	_hpBg2 = slot->getState(this).sprite;

	_visibleHp = false;
}

void CCSkeletalSprite::setUnit() {
	if (_visibleHp != _hp->isVisible()) {
		_hp->setVisible(_visibleHp);
		_hpBg1->setVisible(_visibleHp);
		_hpBg2->setVisible(_visibleHp);
	}
}

void CCSkeletalSprite::draw() {
	if (_hp) {
		setUnit();
	}

	for (auto it:_rootSpriteList) {
		it->draw();
	}

	if (_debugBone) {
		glLineWidth(2);
		queue<CCBone *> que;
		que.push(m_rootBone);

		while (!que.empty()) {
			CCBone *bone = que.front();
			que.pop();
			CCAffineTransform t = bone->getState(this).transform;

			float x = bone->getLength() * t.a + t.tx;
			float y = bone->getLength() * -t.c + t.ty;

			ccDrawColor4B(255, 0, 0, 255);
			ccDrawLine(ccp(t.tx, t.ty), ccp(x, y));

			if (bone == m_rootBone) {
				ccPointSize(10);//Root bone size is 10
				ccDrawColor4B(0, 0, 255, 255); // Root bone is blue.
			}
			else {
				ccPointSize(4);
				ccDrawColor4B(0, 255, 0, 255);
			}

			ccDrawPoint(ccp(t.tx, t.ty));

			for (auto it:bone->getChildren()) {
				que.push(it);
			}

		}
	}
	if (_debugSlots) {
		for (auto it: _rootSpriteList) {
			CCArray *ary = it->getChildren();
			for (int i = 0; i < ary->count(); i++) {
				CCSpineSprite *child = (CCSpineSprite *) ary->objectAtIndex(i);
				if (child->isVisible() && child->getOpacity()) {
					child->debug();
				}
			}
		}
	}
}

CCPoint CCSkeletalSprite::getBonePosition(const char *boneName) {
	// basic check
	if (!m_skeleton) {
		return CCPointZero;
	}

	// get bone
	CCBone *bone = m_skeleton->getBone(boneName);
	if (!bone) {
		return CCPointZero;
	}

	// get bone node
	CCBone::State &state = bone->getState(this);


	CCAffineTransform t = state.transform;

	if (_isFlipX) {
		t.tx = -t.tx;
	}

	return ccp(t.tx, t.ty);
}

CCPoint CCSkeletalSprite::getBonePositionRelativeToWorld(const char *boneName) {
	CCPoint pos = getBonePosition(boneName);
	return CCPointApplyAffineTransform(pos, nodeToParentTransform());
}

void CCSkeletalSprite::stopAnimation(bool restore) {
	// no animation? return
	if (!m_animation) {
		return;
	}

	//TODO  don't relese animation for blend

	// clear animation
	//m_animation->release();
	//m_animation = NULL;

	// restore original state
	if (m_rootBone && restore) {
		syncStates(m_rootBone);
	}
}

void CCSkeletalSprite::playAnimation(CCSkeletalAnimation *anim, blendType type, float blendTime) {
	// stop current
	//stopAnimation(false);

	// hold this animation
	anim->retain();

	if (m_animation == anim && type == tFadeInOut) {
		//CCLOG("spine warning | the animation : %s is multi play,and used fadeInOut,are you sure it's right?", anim->getName());
		type = tSwitch;//TODO
	}

	if (type == tSwitch) {
		CC_SAFE_RELEASE(m_animation);
		CC_SAFE_RELEASE_NULL(_preAnimation);
	}
	else {
		if (!_preAnimation || _blendPassTime >= _blendTime * 0.5f) {//如果之前没有融合动画，或者之前的融合进度已经超过了一半 则更换需要融合的动画
			CC_SAFE_RELEASE(_preAnimation);
			_preLoop = m_loop;
			_preFrameTime = m_frameTime;
			_preAnimation = m_animation;
		}
		else {
			CC_SAFE_RELEASE(m_animation);
		}
	}
	_type = type;
	_blendPassTime = 0.0f;
	_blendTime = blendTime;
	m_animation = anim;

	//init for first frame
	setupFirstFrameState();
}

void CCSkeletalSprite::playAnimation(const char *animName, blendType type, float blendTime) {
	CCSkeletalAnimation *anim = m_skeleton->getAnim(animName);
	if (anim) {
		playAnimation(anim, type, blendTime);
	}
	else {
		CCLOG("spine error | animation:%s is not find!!", animName);
	}
}

void CCSkeletalSprite::setupFirstFrameState() {
	// reset time to zero
	m_frameTime = 0;

	// reset flag
	m_paused = false;

	// set animation to the very beginning
	//setFrame(m_frameTime);
}

void CCSkeletalSprite::addBlendAnimation(const char *animName, float alpha) {
	CCSkeletalAnimation *anim = m_skeleton->getAnim(animName);
	CC_SAFE_RELEASE(_blendAnimation);
	_blendAnimation = anim;
	_blendAnimation->retain();
	_blendAlpha = alpha;
	_blendFrameTime = 0;
	_blendLoop = 1;
}

void CCSkeletalSprite::update(float delta, float deltaForBlend) {
	// is paused?
	if (m_paused)
		return;

	// basic checking
	if (!m_animation || !m_skeleton)
		return;

	m_animation->applyTo(this, m_frameTime, delta);//events

	if (_type != tSwitch && _preAnimation && _blendPassTime + delta <= _blendTime) {
		_blendPassTime += delta;
		float alpha = _blendPassTime / _blendTime;
		updateAnim(m_animation, m_frameTime, m_loop, delta, alpha);
		updateAnim(_preAnimation, _preFrameTime, _preLoop, delta, 1.0f - alpha);
	}
	else {
		CC_SAFE_RELEASE_NULL(_preAnimation);
		updateAnim(m_animation, m_frameTime, m_loop, delta);
	}

	if (_blendAnimation) {
		if (deltaForBlend < 0) {
			deltaForBlend = delta;
		}
		updateAnim(_blendAnimation, _blendFrameTime, _blendLoop, deltaForBlend, _blendAlpha);
		if (_blendLoop == 0) {
			_blendAnimation->release();
			_blendAnimation = NULL;
		}
	}

	syncStates(m_rootBone);
}

void CCSkeletalSprite::updateAnim(cocos2d::CCSkeletalAnimation *animation, float &frameTime, int &loop, float delta, float alpha) {
	frameTime += delta;
	if (frameTime > animation->getDuration()) {
		if (loop < 0) {
			frameTime = fmod(frameTime, animation->getDuration());
		} else if (loop > 1) {
			loop--;
			frameTime = fmod(frameTime, animation->getDuration());
		} else {
			loop = 0;
		}
	}
	setFrame(animation, frameTime, alpha, delta);
}

void CCSkeletalSprite::setFrame(CCSkeletalAnimation *animation, float time, float alpha, float deltaTime) {
	CCSkeletalAnimation::TransformPtrList &tList = animation->getTransformList();
	for (CCSkeletalAnimation::TransformPtrList::iterator iter = tList.begin(); iter != tList.end(); iter++) {
		// calculate info
		CCTransform *t = *iter;
		t->populateFrame(time, deltaTime);

		// apply frame
		t->applyTo(this, alpha);
	}
}

void CCSkeletalSprite::setSkeleton(CCSkeleton *s) {
	// hold skeleton
	s->retain();
	m_skeleton = s;

	// re-create bone nodes
	m_rootBone = m_skeleton->getRootBone();

	applySkin("default");
}

void CCSkeletalSprite::clearBoneStates(CCBone *bone) {
	bone->clearState(this);

	CCBone::BonePtrList &children = bone->getChildren();
	for (CCBone::BonePtrList::iterator iter = children.begin(); iter != children.end(); iter++) {
		clearBoneStates(*iter);
	}
}

void CCSkeletalSprite::syncStates(CCBone *bone) {

	CCBone::State &boneState = bone->getState(this);

	bone->nodeToParentTransform(boneState);

	CCBone *parent = bone->getParent();

	if (parent) {
		boneState.transform = CCAffineTransformConcat(boneState.transform, parent->getState(this).transform);
	}

	for (auto it: bone->getSlotList()) {
		CCSlot::State &state = it->getState(this);

		//syncAttachmentStates
		CCSlot::State &original = it->getOriginalState();
		CCSpineSprite *sprite = state.sprite;
		const char *s = it->getName();

		if (state.activeAttachmentName != state.preAttachmenName) {
			setSlotAttachment(it->getName(), state.activeAttachmentName, false);
			state.preAttachmenName = state.activeAttachmentName;
		}
		if (sprite) {
			sprite->setColor(state.color);
			sprite->setOpacity(sprite->getOpacity() * _opacity / 255.f);

			//syncBoneStates
			state.sprite->setTranformToBatch(CCAffineTransformConcat(state.sprite->CCNode::nodeToParentTransform(), boneState.transform));
		}
	}

	for (auto it: bone->getChildren()) {
		syncStates(it);
	}
}

void CCSkeletalSprite::cleanSlotStates() {
	CCSkeleton::SlotPtrList &slotDisplayList = m_skeleton->getSlotDisplayList();
	for (CCSkeleton::SlotPtrList::iterator iter = slotDisplayList.begin(); iter != slotDisplayList.end(); iter++) {
		(*iter)->clearState(this);
	}
}

void CCSkeletalSprite::createSprites(CCSprite *sprite, bool &lastAdditive, bool &nowAdditive) {
	CCSpriteBatchNode *rootSprite = NULL;
	if (_rootSpriteList.empty()) {
		CCTexture2D *texture = sprite->getTexture();
		rootSprite = CCSpriteBatchNode::createWithTexture(texture);
		_rootSpriteList.push_back(rootSprite);
		rootSprite->retain();
		lastAdditive = nowAdditive;
		if (nowAdditive) {
			ccBlendFunc blendFunc;
			if (texture->hasPremultipliedAlpha()) {
				blendFunc.src = GL_ONE;
			}
			else {
				blendFunc.src = GL_SRC_COLOR;
			}
			blendFunc.dst = GL_ONE;
			rootSprite->setBlendFunc(blendFunc);
			//sprite->setBlendFunc(blendFunc);
		}
	}
	else {
		rootSprite = (CCSpriteBatchNode *) *_rootSpriteList.rbegin();
		if (rootSprite->getTexture()->getName() != sprite->getTexture()->getName() || lastAdditive != nowAdditive) {
			CCTexture2D *texture = sprite->getTexture();
			rootSprite = CCSpriteBatchNode::createWithTexture(texture);
			_rootSpriteList.push_back(rootSprite);
			rootSprite->retain();
			if (nowAdditive) {
				ccBlendFunc blendFunc;
				if (texture->hasPremultipliedAlpha()) {
					blendFunc.src = GL_ONE;
				}
				else {
					blendFunc.src = GL_SRC_COLOR;
				}
				blendFunc.dst = GL_ONE;
				blendFunc.dst = GL_ONE;
				rootSprite->setBlendFunc(blendFunc);
				//sprite->setBlendFunc(blendFunc);
			}
			lastAdditive = nowAdditive;
		}
	}
	rootSprite->addChild(sprite);
}

void CCSkeletalSprite::createSlotSprites() {
	bool lastAdditive;

	CCSkeleton::SlotPtrList &slotDisplayList = m_skeleton->getSlotDisplayList();
	for (CCSkeleton::SlotPtrList::iterator iter = slotDisplayList.begin(); iter != slotDisplayList.end(); iter++) {

		// get slot active attachment image file name
		CCSlot *slot = *iter;
		// create slot sprite
		CCSlot::State &state = slot->getState(this);
		CCAttachment *attachment = _skin->getFirstAttachment(slot);
		if (!attachment) {
			CCSkin *defaultSkin = m_skeleton->getSkin("default");
			attachment = defaultSkin->getFirstAttachment(slot);

			if (!attachment) {
				CCSkin *defaultSkin = m_skeleton->getSkin("human");
				attachment = defaultSkin->getFirstAttachment(slot);
			}
		}

		bool nowAdditive = slot->getOriginalState().additive;

		if (attachment) {
			CCSpriteFrame *psf = attachment->createRelatedSpriteFrame();
			if (psf) {
				CCSpineSprite *sprite = CCSpineSprite::createWithSpriteFrame(psf);
				attachment->synchro(sprite);
				state.sprite = sprite;
				state.preAttachmenName = state.activeAttachmentName;
				createSprites(sprite, lastAdditive, nowAdditive);

				const char *s = slot->getOriginalState().activeAttachmentName;
				if (!s || strlen(s) == 0) {
					sprite->setVisible(false);
				}
			} else {
				_DHLOG("warning:not found texture %s", attachment->getPath());
			}
		} else {
			_DHLOG("warning:empty slot: %s", slot->getName());
		}
	}
}

void CCSkeletalSprite::setBonePosition(const char *boneName, float x, float y, bool fixed) {
	// basic check
	if (!m_skeleton)
		return;

	// get bone
	CCBone *bone = m_skeleton->getBone(boneName);
	if (!bone)
		return;

	// set
	CCBone::State &state = bone->getState(this);
	state.x = x;
	state.y = y;
	if (fixed)
		bone->addFlag(CCBone::FIXED_POSITION);
	else
		bone->removeFlag(CCBone::FIXED_POSITION);
}

void CCSkeletalSprite::setBoneRotation(const char *boneName, float angle, bool fixed) {
	// basic check
	if (!m_skeleton)
		return;

	// get bone
	CCBone *bone = m_skeleton->getBone(boneName);
	if (!bone)
		return;

	// set
	CCBone::State &state = bone->getState(this);
	state.rotation = angle;
	if (fixed)
		bone->addFlag(CCBone::FIXED_ROTATION);
	else
		bone->removeFlag(CCBone::FIXED_ROTATION);
}

void CCSkeletalSprite::setBoneScale(const char *boneName, float scaleX, float scaleY, bool fixed) {
	// basic check
	if (!m_skeleton)
		return;

	// get bone
	CCBone *bone = m_skeleton->getBone(boneName);
	if (!bone)
		return;

	// set
	CCBone::State &state = bone->getState(this);
	state.scaleX = scaleX;
	state.scaleY = scaleY;
	if (fixed)
		bone->addFlag(CCBone::FIXED_SCALE);
	else
		bone->removeFlag(CCBone::FIXED_SCALE);
}

void CCSkeletalSprite::setSlotColor(const char *slotName, const ccColor4B &color, bool fixed) {
	// basic check
	if (!m_skeleton)
		return;

	// get slot
	CCSlot *slot = m_skeleton->getSlot(slotName);
	if (!slot)
		return;

	// set
	CCSlot::State &state = slot->getState(this);
	state.color = color;
	state.sprite->setColor(color);
	if (fixed)
		slot->addFlag(CCSlot::FIXED_COLOR);
	else
		slot->removeFlag(CCSlot::FIXED_COLOR);
}

void CCSkeletalSprite::setSlotAttachment(const char *slotName, const char *attachmentName, bool fixed) {
	// basic check
	if (!m_skeleton)
		return;

	// get slot
	CCSlot *slot = m_skeleton->getSlot(slotName);
	if (!slot)
		return;

	bool needHide = (!attachmentName || strlen(attachmentName) == 0);
	if (slot->getState(this).sprite) {
		slot->getState(this).sprite->setVisible(!needHide);
	}

	if (needHide) {
		return;
	}

	// set
	CCSlot::State &state = slot->getState(this);
	CCAttachment *attachment = slot->getActiveAttachment(this);
	if (attachment) {
		attachment->setPath(attachmentName);
		CCSpriteFrame *psf = attachment->createRelatedSpriteFrame();
		if (psf) {
			if(state.sprite) {
				state.sprite->setDisplayFrame(psf);
			} else {
				state.sprite = CCSpineSprite::createWithSpriteFrame(psf);
				bool lastAdditive;
				createSprites(state.sprite, lastAdditive, slot->getOriginalState().additive);
			}


			if (fixed)
				slot->addFlag(CCSlot::FIXED_ATTACHMENT);
			else
				slot->removeFlag(CCSlot::FIXED_ATTACHMENT);
		} else {
			if(state.sprite) {
				state.sprite->setVisible(false);
			}
		}
	}
	else {
		CCLOG("CCSkeletalSprite::setSlotAttachment: the attachment %s is not found", attachmentName);
	}
}

void CCSkeletalSprite::applySkin(const char *skinName) {
	// basic check
	if (!m_skeleton)
		return;

	// get skin
	CCSkin *skin = m_skeleton->getSkin(skinName);
	if (!skin) {
		CCLOG("CCSkeletalSprite::applySkin: the skin %s is not found", skinName);
		return;
	}
	cleanOldSkin();

	_skin = skin;

	createSlotSprites();

	syncStates(m_rootBone);
}

void CCSkeletalSprite::cleanOldSkin() {
	CCSkeleton::SlotPtrList &slotDisplayList = m_skeleton->getSlotDisplayList();
	for (auto iter = slotDisplayList.begin(); iter != slotDisplayList.end(); iter++) {
		CCSlot *slot = *iter;
		CCSlot::State &state = slot->getState(this);
		if (state.sprite) {
			state.sprite->setVisible(false);
		}
	}

	for (auto it:_rootSpriteList) {
		it->setVisible(false);
	}
}

void CCSkeletalSprite::setFlipX(bool bFlipX) {
	if (bFlipX != _isFlipX) {
		_isFlipX = bFlipX;
		for (auto it:_rootSpriteList) {
			CCArray *ary = it->getChildren();
			for (int i = 0; i < ary->count(); i++) {
				CCSpineSprite *child = (CCSpineSprite *) ary->objectAtIndex(i);
				child->setFlipX(bFlipX);
			}
		}
	}
}

void CCSkeletalSprite::setOpacity(GLubyte opacity) {
	if (opacity != _opacity) {
		_opacity = opacity;
		for (auto it:_rootSpriteList) {
			CCArray *ary = it->getChildren();
			for (int i = 0; i < ary->count(); i++) {
				CCSpineSprite *child = (CCSpineSprite *) ary->objectAtIndex(i);
				child->setDirty(true);
				child->setOpacity(opacity);
			}
		}
	}
}

void CCSkeletalSprite::setShaderProgram(cocos2d::CCGLProgram *pShaderProgram) {//TODO
	for (auto it: _rootSpriteList) {
		it->setShaderProgram(pShaderProgram);
	}
}

CCRect CCSkeletalSprite::boundingBox() {

	float minX = FLT_MAX, minY = FLT_MAX, maxX = FLT_MIN, maxY = FLT_MIN;

	for (auto it: m_skeleton->getSlotDisplayList()) {
		CCSpineSprite *sprite = it->getState(this).sprite;
		if (sprite && sprite->isVisible() && sprite->getOpacity()) {
			ccV3F_C4B_T2F_Quad quad = sprite->getQuad();
			CCPoint vertices[4] = {
					ccp( quad.bl.vertices.x, quad.bl.vertices.y ),
					ccp( quad.br.vertices.x, quad.br.vertices.y ),
					ccp( quad.tr.vertices.x, quad.tr.vertices.y ),
					ccp( quad.tl.vertices.x, quad.tl.vertices.y ),
			};
			for (int i = 0; i < 4; i++) {
				minX = min(minX, vertices[i].x);
				minY = min(minY, vertices[i].y);
				maxX = max(maxX, vertices[i].x);
				maxY = max(maxY, vertices[i].y);
			}
		}
	}

	CCRect rect = CCRectMake(minX, minY, maxX - minX, maxY - minY);

	return CCRectApplyAffineTransform(rect, nodeToParentTransform());
}

void CCSkeletalSprite::visit() {
	CCNode::visit();
	if (_debugBoundingBox) {
		CCRect rect = boundingBox();

		ccDrawColor4B(0, 255, 255, 255);

		// draw bounding box
		CCPoint vertices[4] = {
				ccp( rect.getMinX(), rect.getMinY() ),
				ccp( rect.getMaxX(), rect.getMinY() ),
				ccp( rect.getMaxX(), rect.getMaxY() ),
				ccp( rect.getMinX(), rect.getMaxY() )
		};
		ccDrawPoly(vertices, 4, true);
	}
}

NS_CC_END
